﻿//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Microsoft Public License.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace DataServiceProvider
{
    using System;
    using System.Collections.Generic;
    using System.Data.Services.Providers;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;

    /// <summary>Expression visitor which translates calls to methods on the <see cref="DataServiceProviderMethods"/> class
    /// into expressions which can be evaluated by LINQ to Objects.</summary>
    internal class DSPExtensionPropertyAccessVisitor : ExpressionVisitor
    {
        /// <summary>MethodInfo for object DataServiceProviderMethods.GetValue(this object value, ResourceProperty property).</summary>
        internal static readonly MethodInfo GetValueMethodInfo = typeof(DataServiceProviderMethods).GetMethod(
            "GetValue",
            BindingFlags.Static | BindingFlags.Public,
            null,
            new Type[] { typeof(object), typeof(ResourceProperty) },
            null);

        /// <summary>MethodInfo for IEnumerable&lt;T&gt; DataServiceProviderMethods.GetSequenceValue(this object value, ResourceProperty property).</summary>
        internal static readonly MethodInfo GetSequenceValueMethodInfo = typeof(DataServiceProviderMethods).GetMethod(
            "GetSequenceValue",
            BindingFlags.Static | BindingFlags.Public,
            null,
            new Type[] { typeof(object), typeof(ResourceProperty) },
            null);

        /// <summary>MethodInfo for Convert.</summary>
        internal static readonly MethodInfo ConvertMethodInfo = typeof(DataServiceProviderMethods).GetMethod(
            "Convert",
            BindingFlags.Static | BindingFlags.Public);

        /// <summary>MethodInfo for TypeIs.</summary>
        internal static readonly MethodInfo TypeIsMethodInfo = typeof(DataServiceProviderMethods).GetMethod(
            "TypeIs",
            BindingFlags.Static | BindingFlags.Public);

        private IDataServiceQueryProvider queryProvider;

        /// <summary>Method which translates an expression using the <see cref="DataServiceProviderMethods"/> methods
        /// into a new expression which can be evaluated by LINQ to Objects.</summary>
        /// <param name="expression">The expression to translate.</param>
        /// <returns>The translated expression.</returns>
        public static Expression TranslateExpression(Expression expression, IDataServiceQueryProvider queryProvider)
        {
            DSPExtensionPropertyAccessVisitor visitor = new DSPExtensionPropertyAccessVisitor();
            visitor.queryProvider = queryProvider;
            return visitor.Visit(expression);
        }

        /// <summary>
        /// MethodCallExpression visit method
        /// </summary>
        /// <param name="m">The MethodCallExpression expression to visit</param>
        /// <returns>The visited MethodCallExpression expression </returns>
        internal override Expression VisitMethodCall(MethodCallExpression m)
        {
            if (m.Method == GetValueMethodInfo)
            {
                // Arguments[0] - the resource to get property value of - we assume it's a DSPEntity
                // Arguments[1] - the ResourceProperty to get value of

                ResourceProperty property = ((ConstantExpression)m.Arguments[1]).Value as ResourceProperty;
                var extension = property.CustomState as DSPMetadataModelExtension.ExtendedResourcePropertyAnnotation;
                if (extension != null)
                {
                    return Expression.Call(
                        Expression.Constant(this.queryProvider),
                        typeof(IDataServiceQueryProvider).GetMethod("GetPropertyValue"),
                        m.Arguments[0],
                        m.Arguments[1]);
                }
            }

            return base.VisitMethodCall(m);
        }
    }
}
